iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Software Development

Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app系列 第 29

Android Fragment 實戰進階:BottomNavigationView 打造 App 底部導覽列

  • 分享至 

  • xImage
  •  

依 Day 27~28 已經進入 RecyclerView、Firebase、Fragment,我建議 Day 29 接著把 Fragment 往「真正 App 架構」推進,主題放在 Fragment 之間切換 + BottomNavigationView,也能自然銜接後面的完整專案。Android 官方 Fragment 文件: ⁠Android Developers — Fragments

Day 29|Android Fragment 實戰進階:BottomNavigationView 打造 App 底部導覽列

前一天我們已經認識了 Fragment,也實際建立 Fragment1、Fragment2、Fragment3,開始把一個 Activity 拆成不同功能畫面。

但做到這裡會遇到下一個問題:

Fragment 都建立好了,使用者到底要怎麼切換?

今天就來完成 Android App 很常見的一種介面:

BottomNavigationView + Fragment

也就是我們在許多 App 底部看到的導覽列。

一、今天要完成什麼?

今天預計完成這樣的 App 架構:

┌─────────────────────────────┐
│                             │
│                             │
│        Fragment 畫面         │
│                             │
│   Fragment1 / 2 / 3         │
│                             │
├─────────────────────────────┤
│   🏠        📋        👤     │
│   首頁       資料       我的  │
└─────────────────────────────┘

使用者按下底部不同按鈕:

首頁

Fragment1
資料

Fragment2
我的

Fragment3

整個過程不需要一直開新的 Activity。

二、Activity 與 Fragment 再複習一次

前面我們學 Android 時,大部分畫面都是使用:

MainActivity
SelectActivity
FirebaseActivity
...

例如:

val intent = Intent(this, SelectActivity::class.java)
startActivity(intent)

這代表:

MainActivity

└──── Intent ────▶ SelectActivity

每切換一個主要畫面,就可能建立另一個 Activity。

但是 Fragment 的概念不同。

一個 Activity 裡面可以放很多 Fragment:

MainActivity

├── Fragment1
├── Fragment2
└── Fragment3

所以 MainActivity 比較像是一個「容器」。

真正顯示給使用者看的內容,可以交給 Fragment。

三、什麼是 Fragment?

Fragment 可以把它想像成:

Activity 裡面的一小塊 UI 與功能模組。

例如一個 App:

MainActivity

├── HomeFragment

├── DataFragment

└── ProfileFragment

當使用者切換頁面時:

MainActivity 不換


只替換 Fragment

因此畫面的結構會變成:

┌──────────────────────────┐
│ MainActivity │
│ │
│ ┌──────────────────────┐ │
│ │ │ │
│ │ Fragment │ │
│ │ │ │
│ └──────────────────────┘ │
│ │
│ BottomNavigationView │
└──────────────────────────┘

這也是為什麼 Fragment 很適合拿來製作 App 的主要分頁。

四、今天的三個 Fragment

延續昨天的程式,我們可以準備:

Fragment1
Fragment2
Fragment3

例如:

class Fragment1 : Fragment(R.layout.fragment_1) {
}

第二個:

class Fragment2 : Fragment(R.layout.fragment_2) {
}

第三個:

class Fragment3 : Fragment(R.layout.fragment_3) {
}

每一個 Fragment 都有自己的 Layout:

Fragment1

fragment_1.xml
Fragment2

fragment_2.xml
Fragment3

fragment_3.xml

這樣每個 Fragment 就能負責自己的畫面。

五、建立 Fragment 顯示區域

接下來 MainActivity 必須留一塊地方給 Fragment。

例如:

可以把 FrameLayout 想成:

┌──────────────────────────┐
│ │
│ │
│ fragmentContainer │
│ │
│ │
└──────────────────────────┘

Fragment1、Fragment2、Fragment3 都會輪流顯示在這裡。

六、FragmentManager 登場

要控制 Fragment,就會使用:

supportFragmentManager

它就是 Fragment 的管理者。

例如我們要把 Fragment1 放進 fragmentContainer:

supportFragmentManager
.beginTransaction()
.replace(R.id.fragmentContainer, Fragment1())
.commit()

拆開來看:

supportFragmentManager

取得 FragmentManager。

接著:

.beginTransaction()

開始一次 Fragment 畫面操作。

然後:

.replace(
R.id.fragmentContainer,
Fragment1()
)

把指定的 Fragment 放進容器。

最後:

.commit()

正式執行這次 FragmentTransaction。

所以完整流程就是:

supportFragmentManager

beginTransaction()

replace()

commit()

七、把切換 Fragment 包成函式

如果每次切換都寫:

supportFragmentManager
.beginTransaction()
.replace(R.id.fragmentContainer, Fragment1())
.commit()

程式很快就會出現很多重複內容。

因此可以整理成:

private fun replaceFragment(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.fragmentContainer, fragment)
.commit()
}

之後只需要:

replaceFragment(Fragment1())

或:

replaceFragment(Fragment2())

或:

replaceFragment(Fragment3())

程式會乾淨很多。

八、BottomNavigationView 是什麼?

BottomNavigationView 就是 Android 常見的底部導覽列。

例如:

┌─────────────────────────────┐
│ │
│ App Content │
│ │
├─────────────────────────────┤
│ 🏠 📋 👤 │
│ 首頁 資料 我的 │
└─────────────────────────────┘

它通常適合放:

3~5 個主要功能。

例如:

首頁
搜尋
通知
收藏
個人資料

今天我們使用三個:

首頁
資料
我的

九、建立 Bottom Navigation Menu

建立:

res
└── menu
└── bottom_menu.xml

內容:

這裡定義三個按鈕:

nav_home
nav_data
nav_profile

接下來再把它交給 BottomNavigationView。

十、加入 BottomNavigationView

在 activity_main.xml 加入:

<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_menu" />

其中最重要的是:

app:menu="@menu/bottom_menu"

也就是告訴 BottomNavigationView:

你的按鈕內容放在 bottom_menu.xml。

因此:

bottom_menu.xml


BottomNavigationView

├── 首頁
├── 資料
└── 我的

十一、監聽 BottomNavigationView

接下來在 Kotlin 裡取得:

val bottomNavigationView =
findViewById(
R.id.bottomNavigationView
)

接著設定:

bottomNavigationView
.setOnItemSelectedListener { item ->
when (item.itemId) {
R.id.nav_home -> {
replaceFragment(Fragment1())
true
}
R.id.nav_data -> {
replaceFragment(Fragment2())
true
}
R.id.nav_profile -> {
replaceFragment(Fragment3())
true
}
else -> false
}
}

這段就是今天最重要的核心。

十二、完整切換流程

當使用者按下:

🏠 首頁

會觸發:

R.id.nav_home

然後:

replaceFragment(Fragment1())

最後:

MainActivity


FragmentManager


replace()


Fragment1

如果按下:

📋 資料

則變成:

MainActivity


FragmentManager


Fragment2

再按:

👤 我的

變成:

MainActivity


FragmentManager


Fragment3

這樣就完成一個基本的 App 分頁架構。

十三、MainActivity 完整範例

整理之後:

package com.example.fragmentdemo
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.google.android.material.bottomnavigation.BottomNavigationView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 載入 MainActivity 的版面配置
setContentView(R.layout.activity_main)
// 第一次進入 Activity 時顯示首頁 Fragment。
// savedInstanceState != null 通常代表 Activity 正在重建,
// 此時 FragmentManager 會協助還原原本的 Fragment,
// 因此不要再建立一次 Fragment1。
if (savedInstanceState == null) {
replaceFragment(Fragment1())
}
// 取得底部導覽列
val bottomNavigationView =
findViewById(
R.id.bottomNavigationView
)
// 監聽使用者點擊底部導覽列
bottomNavigationView.setOnItemSelectedListener { item ->
when (item.itemId) {
// 首頁
R.id.nav_home -> {
// 顯示 Fragment1
replaceFragment(Fragment1())
// true 代表已處理這次點擊事件
true
}
// 資料頁
R.id.nav_data -> {
// 顯示 Fragment2
replaceFragment(Fragment2())
true
}
// 個人頁面
R.id.nav_profile -> {
// 顯示 Fragment3
replaceFragment(Fragment3())
true
}
// 如果沒有符合的 Menu ID,就不處理
else -> false
}
}
}
/**
* 切換 Fragment 的共用函式。
*
* fragment:
* 要顯示在 fragmentContainer 裡面的 Fragment。
*/
private fun replaceFragment(fragment: Fragment) {
// 取得 FragmentManager,
// 開始 FragmentTransaction。
supportFragmentManager
.beginTransaction()
// 將 fragmentContainer 目前顯示的 Fragment
// 替換成傳入的新 Fragment。
.replace(
R.id.fragmentContainer,
fragment
)
// 提交這次 FragmentTransaction。
.commit()
}
}

這樣 MainActivity 的責任就很明確:

MainActivity

├── 管理 BottomNavigationView

└── 管理 Fragment 切換

十四、為什麼不要全部塞在 MainActivity?

假設我們沒有 Fragment。

一開始 MainActivity 只有:

登入

後來加入:

Firebase
RecyclerView
個人資料
設定
收藏
搜尋

MainActivity 很可能慢慢變成:

MainActivity.kt
100 行
300 行
500 行
800 行
1000 行...

最後什麼功能都放在同一個 Activity。

這樣程式會越來越難維護。

使用 Fragment 後可以拆成:

MainActivity

├── HomeFragment

├── FirebaseFragment
├── ListFragment
├── ProfileFragment
└── SettingFragment

每一個 Fragment 負責自己的功能。

這就是 Android App 開始走向模組化的重要一步。

十五、把前幾天學過的東西放進 Fragment

這也是 Fragment 真正有趣的地方。

前面我們已經學過:

RecyclerView
Room
Firebase
Intent
SQLite
感測器

現在這些東西都可以開始整合。

例如:

MainActivity

├── HomeFragment

│ └── 首頁資訊

├── FirebaseFragment

│ └── Firebase 資料

└── ListFragment
└── RecyclerView

甚至:

Firebase

Fragment

RecyclerView

Adapter

CardView

這時候前面每天學的技術,就不再是一個一個分開的小範例,而是開始可以組合成真正的 App。

十六、Activity 與 Fragment 怎麼選?

可以簡單這樣理解:

功能 Activity Fragment
App 主入口 ✔
完全獨立流程 ✔
同一畫面的分頁 ✔
Bottom Navigation ✔
Tab 分頁 ✔
可重複 UI 模組 ✔
App 主要功能頁 ✔ ✔

不是說用了 Fragment 就不能用 Activity。

真正的 App 通常會兩者一起使用。

例如:

LoginActivity


MainActivity

├── HomeFragment
├── DataFragment
└── ProfileFragment

登入可以是一個 Activity。

登入成功之後進入 MainActivity。

MainActivity 裡面再利用 Fragment 管理主要功能。

十七、今天的 App 架構

把今天的內容整理起來:

               MainActivity
                    │
      ┌─────────────┴─────────────┐
      │                           │
      ▼                           ▼

fragmentContainer BottomNavigationView
│ │
│ ┌───────────┼───────────┐
│ │ │ │
▼ ▼ ▼ ▼
Fragment nav_home nav_data nav_profile
│ │ │ │
│ ▼ ▼ ▼
│ Fragment1 Fragment2 Fragment3
│ │ │ │
└───────────────┴───────────┴───────────┘


FragmentManager


replace()

這就是今天最重要的觀念:

Activity 負責承載,Fragment 負責畫面,BottomNavigationView 負責讓使用者選擇要看的 Fragment。

十八、Fragment 與 Activity 最大差異

Fragment 最大的特色之一,就是它本身不是完全獨立存在。

Fragment 必須依附在 Activity 上。

所以:

Activity

└── Fragment

而不是:

Fragment

└── Activity

Fragment 也有自己的生命週期,例如:

onCreate()

onCreateView()

onViewCreated()

onStart()

onResume()

離開畫面時:

onPause()

onStop()

onDestroyView()

onDestroy()

這裡有一個很重要的觀念:

Fragment 本身的生命週期與 Fragment View 的生命週期並不完全相同。

這件事情之後做到 ViewBinding、LiveData 或 Navigation Component 時會非常重要。

十九、今天學到什麼?

今天把昨天建立好的 Fragment 真正拿來使用。

從:

建立 Fragment

進一步做到:

建立 Fragment

FragmentManager

FragmentTransaction

replace()

BottomNavigationView

Fragment1 / Fragment2 / Fragment3

也正式從:

單一 Activity 範例程式

開始走向:

真正 Android App 的多頁面架構

二十、今日重點整理

今天最重要的幾個關鍵字:

Fragment
FragmentManager
FragmentTransaction
replace()
commit()
FrameLayout
BottomNavigationView
Menu
setOnItemSelectedListener

其中 Fragment 切換最核心的程式就是:

supportFragmentManager
.beginTransaction()
.replace(
R.id.fragmentContainer,
fragment
)
.commit()

BottomNavigationView 則負責決定:

使用者按哪一個按鈕

顯示哪一個 Fragment

最後形成:

         MainActivity
              │
              ▼
   BottomNavigationView
              │
    ┌─────────┼─────────┐
    ▼         ▼         ▼

Fragment1 Fragment2 Fragment3
首頁 資料 我的

到這裡,我們已經不只是會「做一個 Android 畫面」,而是開始思考:

一個 App 的不同功能,應該怎麼拆、怎麼組合、怎麼讓使用者切換。

這也是從 Android 基礎練習走向完整 App 開發很重要的一步。

參考資料

Android Developers — Fragments
https://developer.android.com/guide/fragments

Android Developers — FragmentManager
https://developer.android.com/guide/fragments/fragmentmanager

Android Developers — Fragment lifecycle
https://developer.android.com/guide/fragments/lifecycle

Material Design — Bottom navigation
https://m3.material.io/components/navigation-bar/overview

Material Components Android
https://github.com/material-components/material-components-android

這一篇很適合接在前一天的 Fragment1 / Fragment2 / Fragment3 後面;**Day 30 就可以把 RecyclerView + Firebase + Fragment + BottomNavigationView 全部合起來,當成 30 天系列的整合實戰收尾。**參考: ⁠Android Developers — FragmentManager ⁠Material Design — Navigation Bar

如果要維持你前幾天「圖文並茂」的版型,我也可以把這篇再改成 iThome 可直接貼上的 Markdown 版,並直接生成 Day 29 首圖+Fragment 切換架構圖。


上一篇
Android Fragment 實戰:從生命週期、Fragment 切換到 Activity 資料傳遞
下一篇
Android 學習旅程總整理——從 Kotlin 基礎到 App 實作
系列文
Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言